
"""
 * Copyright 2009 Yogi KENT
 * 
 * This file is main part of Sotetsu.
 * 
 * Sotetsu is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * Sotetsu is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.

"""
import sys,io
#import http.client
import urllib.request

class DICT(dict):
	def __init__(self):
		dict.__init__(self)
	def __missing__(self,key):
		return None


class Daap:
	def __init__(self, server, port=3689):
		#self.conn = http.client.HTTPConnection(server, port)
		self.server = server
		self.port = port

	def getItemList(self):
		#self.conn.request("get", "/databases/1/items?type=music&meta=dmap.itemid,daap.songalbum,daap.songartist,dmap.itemname")
		#resp = conn.getresponse()
		rsp = urllib.request.urlopen("http://{0:s}:{1!s:s}/databases/1/items?type=music&meta=dmap.itemid,daap.songalbum,daap.songartist,dmap.itemname".format(self.server, self.port))

		data = rsp.read()
		br = io.BytesIO(data)
		#byte = br.read(8)
		return self.parseItemList(br)

	def parseItemList(self, buff):

		apsoSize = self.readTag(buff, DATABASESONGS)
		msttSize = self.readTag(buff, STATUS) #dmap.status (int)
		msttData = buff.read(msttSize)
		mutySize = self.readTag(buff, UPDATETYPE) #dmap.updatetype (byte)
		mutyData = buff.read(mutySize)
		mtcoSize = self.readTag(buff, SPECIFIEDTOTALCOUNT) #dmap.specifiedtotalcount (int)
		mtcoData = buff.read(mtcoSize)
		mrcoSize = self.readTag(buff, RETURNEDCOUNT) #dmap.returnedcount (int)
		mrcoData = buff.read(mrcoSize)
		mlclSize = self.readTag(buff, LISTING) #dmap.listing (int)
		#mlclData = buff.read(mlclSize)

		#print(msttData)
		#print("mstt = {0:d}".format(bytes2int(msttData)))
		#print("muty = {0:d}".format(bytes2int(mutyData)))
		#print("mtco = {0:d}".format(bytes2int(mtcoData)))
		#print("mrco = {0:d}".format(bytes2int(mrcoData)))
		##print("mlcl = {0:d}".format(bytes2int(mlclData)))

		mrco = bytes2int(mrcoData)
		print("there are {0:d} items in this server".format(mrco))
		list = []
		for i in range(mrco):
			mlitSize = self.readTag(buff, LISTINGITEM)
			asalSize = self.readTag(buff, SONGALBUM) #album
			asalData = buff.read(asalSize)
			asarSize = self.readTag(buff, SONGARTIST) #artist
			asarData = buff.read(asarSize)
			miidSize = self.readTag(buff, ITEMID) #id
			miidData = buff.read(miidSize)
			minmSize = self.readTag(buff, ITEMNAME) #name
			minmData = buff.read(minmSize)
			item = {}
			item[SONGALBUM] = self.__decode(asalData)
			item[SONGARTIST] = self.__decode(asarData)
			item[ITEMNAME] = self.__decode(minmData)
			item[ITEMID] = bytes2int(miidData)
			list.append(item)

		#print(list)
		return list

	def __decode(self, byte):
		try:
			return byte.decode()
		except UnicodeDecodeError as e:
			return "(Decode Error)"

	def readTag(self, buff, tag):
		"""reads tag's code and size of contents from buff and return the size."""
		code = codeInfo[tag][0]
		tagbyte = buff.read(len(code))
		if tagbyte!=code: raise RuntimeError("miss match tag name {0:s} != {1:s}!".format(tagbyte,code))
		bytes = buff.read(4)
		return bytes2int(bytes)

	def getStream(self, item):
		id = item[ITEMID]
		#fo = self.conn.request("get", "http://{0:s}:{1!s:s}/databases/1/items/{2:s}.mp3".format(self.server,self.port,id))
		rsp = urllib.request.urlopen("http://{0:s}:{1!s:s}/databases/1/items/{2!s:s}.mp3".format(self.server, self.port, id))
		return rsp


	#################################
	### belows are for experiment ###
	#################################
	def __parseList_0(self, total_size):
		"""parse list type tags except for mlcl.  """
		buff = self.buff
		start = buff.tell()
		rtn = DICT()
		# start reading contents of list.
		while buff.tell() - start < total_size:
			(tag,size) = self.__readTag_0()
			type = codeInfo[tag][1]
			data = self.__getData_0(type, size)
			rtn[tag] = data
		return rtn

	def __parseMLCL_0(self, total_size):
		""" for parsing LISTING b'mlcl'. """
		buff = self.buff
		start = buff.tell()
		mlit = []
		# start reading contents of list.
		while buff.tell() - start < total_size:
			(tag,size) = self.__readTag_0()
			assert tag==LISTINGITEM
			type = codeInfo[LISTINGITEM][1]
			mlit.append( self.__getData_0(type, size) )
			#debug
			#if __debug__ and len(mlit)>=100:
				#buff.seek(start+total_size)
				#break
		return { LISTINGITEM: mlit }

	def __getData_0(self, type, size):
		if type==STRING:
			rtn = self.__parseString_0(size)
		elif type==LIST:
			rtn = self.__parseList_0(size)
		elif type==MLCL:
			rtn = self.__parseMLCL_0(size)
		elif type==DATE:
			rtn = self.__parseDate_0(size)
		elif type==VERSION:
			rtn = self.__parseVersion_0(size)
		elif type < 0 or 8<= type:
			raise TypeError("invalid type {0:d}".format(type))
		#elif   type==BYTE  \
			#or type==SHORT \
			#or type==INT   \
			#or type==LONG
		else:
			rtn = self.__parseNum_0(size)
		return rtn

	def __parseString_0(self, size):
		byte = self.buff.read(size)
		try:
			rtn = byte.decode()
		except UnicodeDecodeError as e:
			rtn = "(Decode Error)"
		return rtn
	
	def __parseNum_0(self, size):
		bytes = self.buff.read(size)
		return bytes2int(bytes)

	def __readTag_0(self):
		"""reads tag's code and size of contents from buff and return the size."""
		buff = self.buff
		tag = buff.read(4)
		bytes = buff.read(4)
		return (tag, bytes2int(bytes))

	def getItemList_0(self, taglist):
		namelist = ( codeInfo[tag][2] for tag in taglist )
		names = ",".join( namelist )
		rsp = urllib.request.urlopen("http://{0:s}:{1!s:s}/databases/1/items?type=music&meta={2:s}".format(self.server, self.port, names))
		
		data = rsp.read()
		self.buff = io.BytesIO(data)

		(tag,size) = self.__readTag_0()
		assert tag==DATABASESONGS 
		adbs = self.__parseList_0(size)

		rtn = adbs[LISTING][LISTINGITEM]
		return rtn

	def getItemList_fromBuff_0(self, buff):
		self.buff = buff
		(tag,size) = self.__readTag_0()
		rtn = self.__getData_0(codeInfo[tag][1], size)
		return rtn

	################################
	### for printing parsed data ###
	################################
	def __printTree(self, tree, sp="  "):
		self.sp = sp
		self.__printList(b"Root", tree, 0)

	def __printNum(self, tag, data, n):
		sp = self.sp
		#print(sp*n + tag + " ==> " + data)
		print("{0:s}{1:s} ==> {2:d}".format(sp*n, tag.decode(), data))
	def __printString(self, tag, data, n):
		sp = self.sp
		#print(sp*n + tag + " ==> " + data)
		print("{0:s}{1:s} ==> {2:s}".format(sp*n, tag.decode(), data))
	def __printList(self, tag, data, n):
		sp = self.sp
		#print(sp*n + tag.decode() + " -\ ")
		print("{0:s}{1:s} -↓".format(sp*n, tag.decode()))
		for (key,val) in data.items():
			if isinstance(val, dict):
				if key==LISTING:
					self.__printMLCL(key, val, n+1)
				else:
					self.__printList(key, val, n+1)
				#print(val)
			elif isinstance(val, int):
				self.__printNum(key, val, n+1)
				#print(val)
			elif isinstance(val, str) \
				or isinstance(val, bytes) :
				self.__printString(key, val, n+1)
			else:
				print("{0:s}{1:s} ==> (Unknown {2:s})".format(sp*n, tag.decode(), type(val)))
	def __printMLCL(self, tag, data, n):
		sp = self.sp
		print("{0:s}{1:s} -↓".format(sp*n, tag.decode()))
		for item in data[LISTINGITEM]:
			self.__printList(LISTINGITEM, item, n+1)

def bytes2int(bytes):
	ret = 0
	for b in bytes:
		ret = ret*256 + b
	#for b in reversed(bytes):
		#ret = ret + b*(256**i)
		#i = i+1
	return int(ret)

def main():
	daap = Daap(server, port)
	daap.getItemList()
	daap.getItem(id)

def test():
	br = io.BufferedReader(io.FileIO(sys.argv[1], "r"))
	byte = br.read(4)
	#byte = br.read(6)
	#print( byte.decode())

	print(byte[3])
	print(byte[3]+5)

def test_parseItemList():
	br = io.BufferedReader(io.FileIO(sys.argv[1], "r"))
	daap = Daap("",2)
	daap.parseItemList(br)

(
    LIST,
    MLCL,	# for only LISTING
    STRING,
    BYTE,
    SHORT,
    INT,
    LONG,
    DATE,
    VERSION,
) = range(9)

DICTIONARY = b'mdcl'
STATUS = b'mstt'
ITEMID = b'miid'
ITEMNAME = b'minm'
ITEMKIND = b'mikd'
PERSISTENTID = b'mper'
CONTAINER = b'mcon'
CONTAINERITEMID = b'mcti'
PARENTCONTAINERID = b'mpco'
STATUSSTRING = b'msts'
ITEMCOUNT = b'mimc'
RETURNEDCOUNT = b'mrco'
SPECIFIEDTOTALCOUNT = b'mtco'
LISTING = b'mlcl'
LISTINGITEM = b'mlit'
BAG = b'mbcl'
DICTIONARY = b'mdcl'
SERVERINFORESPONSE = b'msrv'
AUTHENTICATIONMETHOD = b'msaud'
LOGINREQUIRED = b'mslr'
PROTOCOLVERSION = b'mpro'
PROTOCOLVERSION = b'apro'
SUPPORTSUATOLOGOUT = b'msal'
SUPPORTSUPDATE = b'msup'
SUPPORTSPERSISTENTIDS = b'mspi'
SUPPORTSEXTENSIONS = b'msex'
SUPPORTSBROWSE = b'msbr'
SUPPORTSQUERY = b'msqy'
SUPPORTSINDEX = b'msix'
SUPPORTSRESOLVE = b'msrs'
TIMEOUTINTERVAL = b'mstm'
DATABASESCOUNT = b'msdc'
CONTENTCODESRESPONSE = b'mccr'
CONTENTCODESNUMBER = b'mcnm'
CONTENTCODESNAME = b'mcna'
CONTENTCODESTYPE = b'mcty'
LOGINRESPONSE = b'mlog'
SESSIONID = b'mlid'
UPDATERESPONSE = b'mupd'
SERVERREVISION = b'msur'
UPDATETYPE = b'muty'
DELETEDIDLISTING = b'mudl'
SERVERDATABASES = b'avdb'
DATABASEBROWSE	 = b'abro'
BROWSEALBUMLISTUNG	   = b'abal'
BROWSEARTISTLISTING    = b'abar'
BROWSECOMPOSERLISTING = b'abcp'
BROWSEGENRELISTING = b'abgn'
DATABASESONGS = b'adbs'
SONGALBUM = b'asal'
SONGARTIST = b'asar'
SONGSBEATSPERMINUTE = b'asbt'
SONGBITRATE = b'asbr'
SONGCOMMENT = b'ascm'
SONGCOMPILATION = b'asco'
SONGDATEADDED = b'asda'
SONGDATEMODIFIED = b'asdm'
SONGDISCCOUNT = b'asdc'
SONGDISCNUMBER = b'asdn'
SONGDISABLED = b'asdb'
SONGEQPRESET = b'aseq'
SONGFORMAT = b'asfm'
SONGGENRE = b'asgn'
SONGDESCRIPTION = b'asdt'
SONGRELATIVEVOLUME = b'asrv'
SONGSAMPLERATE = b'assr'
SONGSIZE = b'assz'
SONGSTARTTIME  = b'asst'
SONGSTOPTIME  = b'assp'
SONGTIME = b'astm'
SONGTRACKCOUNT = b'astc'
SONGTRACKNUMBER = b'astn'
SONGUSERRATING = b'asur'
SONGYEAR = b'asyr'
SONGDATAKIND = b'asdk'
SONGDATAURL = b'asul'
DATABASEPLAYLISTS = b'aply'
BASEPLAYLIST = b'abpl'
PLAYLISTSONGS = b'apso'
RESOLVE = b'prsv'
RESOLVEINFO = b'arif'
NORM = b'aeNV'
SMART = b'aeSP'

codeInfo = {}
codeInfo[DICTIONARY] =		( DICTIONARY, LIST, "dmap.dictionary" )
codeInfo[STATUS] =		( STATUS, INT, "dmap.status" )
codeInfo[ITEMID] =		( ITEMID, INT, "dmap.itemid" )
codeInfo[ITEMNAME] =		( ITEMNAME, STRING, "dmap.itemname" )
codeInfo[ITEMKIND] =		( ITEMKIND, BYTE, "dmap.itemkind" )
codeInfo[PERSISTENTID] =	( PERSISTENTID, LONG, "dmap.persistentid" )
codeInfo[CONTAINER] =		( CONTAINER, LIST, "dmap.container" )
codeInfo[CONTAINERITEMID] =	( CONTAINERITEMID, INT, "dmap.containeritemid" )
codeInfo[PARENTCONTAINERID] =	( PARENTCONTAINERID, INT, "dmap.parentcontainerid" )
codeInfo[STATUSSTRING] =	( STATUSSTRING, STRING, "dmap.statusstring" )
codeInfo[ITEMCOUNT] =		( ITEMCOUNT, INT, "dmap.itemcount" )
codeInfo[RETURNEDCOUNT] =	( RETURNEDCOUNT, INT, "dmap.returnedcount" )
codeInfo[SPECIFIEDTOTALCOUNT] =	( SPECIFIEDTOTALCOUNT, INT, "dmap.specifiedtotalcount" )
codeInfo[LISTING] =		( LISTING, MLCL, "dmap.listing", LISTINGITEM )
codeInfo[LISTINGITEM] =		( LISTINGITEM, LIST, "dmap.listingitem" )
codeInfo[BAG] =			( BAG, LIST, "dmap.bag" )
codeInfo[DICTIONARY] =		( DICTIONARY, LIST, "dmap.dictionary" )
codeInfo[SERVERINFORESPONSE] =	( SERVERINFORESPONSE, LIST, "dmap.serverinforesponse" )
codeInfo[AUTHENTICATIONMETHOD] =( AUTHENTICATIONMETHOD, BYTE, "dmap.authenticationmethod" )
codeInfo[LOGINREQUIRED] =	( LOGINREQUIRED, BYTE, "dmap.loginrequired" )
codeInfo[PROTOCOLVERSION] =	( PROTOCOLVERSION, VERSION, "dmap.protocolversion" )
codeInfo[PROTOCOLVERSION] =	( PROTOCOLVERSION, VERSION, "daap.protocolversion" )
codeInfo[SUPPORTSUATOLOGOUT] =	( SUPPORTSUATOLOGOUT, BYTE, "dmap.supportsuatologout" )
codeInfo[SUPPORTSUPDATE] =	( SUPPORTSUPDATE, BYTE, "dmap.supportsupdate" )
codeInfo[SUPPORTSPERSISTENTIDS]=( SUPPORTSPERSISTENTIDS, BYTE, "dmap.supportspersistentids" )
codeInfo[SUPPORTSEXTENSIONS] =	( SUPPORTSEXTENSIONS, BYTE, "dmap.supportsextensions" )
codeInfo[SUPPORTSBROWSE] =	( SUPPORTSBROWSE, BYTE, "dmap.supportsbrowse" )
codeInfo[SUPPORTSQUERY] =	( SUPPORTSQUERY, BYTE, "dmap.supportsquery" )
codeInfo[SUPPORTSINDEX] =	( SUPPORTSINDEX, BYTE, "dmap.supportsindex" )
codeInfo[SUPPORTSRESOLVE] =	( SUPPORTSRESOLVE, BYTE, "dmap.supportsresolve" )
codeInfo[TIMEOUTINTERVAL] =	( TIMEOUTINTERVAL, INT, "dmap.timeoutinterval" )
codeInfo[DATABASESCOUNT] =	( DATABASESCOUNT, INT, "dmap.databasescount" )
codeInfo[CONTENTCODESRESPONSE] =( CONTENTCODESRESPONSE, LIST, "dmap.contentcodesresponse" )
codeInfo[CONTENTCODESNUMBER] =	( CONTENTCODESNUMBER, INT, "dmap.contentcodesnumber" )
codeInfo[CONTENTCODESNAME] =	( CONTENTCODESNAME, STRING, "dmap.contentcodesname" )
codeInfo[CONTENTCODESTYPE] =	( CONTENTCODESTYPE, SHORT, "dmap.contentcodestype" )
codeInfo[LOGINRESPONSE] =	( LOGINRESPONSE, LIST, "dmap.loginresponse" )
codeInfo[SESSIONID] =		( SESSIONID, INT, "dmap.sessionid" )
codeInfo[UPDATERESPONSE] =	( UPDATERESPONSE, LIST, "dmap.updateresponse" )
codeInfo[SERVERREVISION] =	( SERVERREVISION, INT, "dmap.serverrevision" )
codeInfo[UPDATETYPE] =		( UPDATETYPE, BYTE, "dmap.updatetype" )
codeInfo[DELETEDIDLISTING] =	( DELETEDIDLISTING, LIST, "dmap.deletedidlisting" )
codeInfo[SERVERDATABASES] =	( SERVERDATABASES, LIST, "daap.serverdatabases" )
codeInfo[DATABASEBROWSE] =	( DATABASEBROWSE, LIST, "daap.databasebrowse	" )
codeInfo[BROWSEALBUMLISTUNG] =	( BROWSEALBUMLISTUNG, LIST, "daap.browsealbumlistung	  " )
codeInfo[BROWSEARTISTLISTING] =	( BROWSEARTISTLISTING, LIST, "daap.browseartistlisting   " )
codeInfo[BROWSECOMPOSERLISTING]=( BROWSECOMPOSERLISTING, LIST, "daap.browsecomposerlisting" )
codeInfo[BROWSEGENRELISTING] =	( BROWSEGENRELISTING, LIST, "daap.browsegenrelisting" )
codeInfo[DATABASESONGS] =	( DATABASESONGS, LIST, "daap.databasesongs" )
codeInfo[SONGALBUM] =		( SONGALBUM, STRING, "daap.songalbum" )
codeInfo[SONGARTIST] =		( SONGARTIST, STRING, "daap.songartist" )
codeInfo[SONGSBEATSPERMINUTE] =	( SONGSBEATSPERMINUTE, SHORT, "daap.songsbeatsperminute" )
codeInfo[SONGBITRATE] =		( SONGBITRATE, SHORT, "daap.songbitrate" )
codeInfo[SONGCOMMENT] =		( SONGCOMMENT, STRING, "daap.songcomment" )
codeInfo[SONGCOMPILATION] =	( SONGCOMPILATION, BYTE, "daap.songcompilation" )
codeInfo[SONGDATEADDED] =	( SONGDATEADDED, DATE, "daap.songdateadded" )
codeInfo[SONGDATEMODIFIED] =	( SONGDATEMODIFIED, DATE, "daap.songdatemodified" )
codeInfo[SONGDISCCOUNT] =	( SONGDISCCOUNT, SHORT, "daap.songdisccount" )
codeInfo[SONGDISCNUMBER] =	( SONGDISCNUMBER, SHORT, "daap.songdiscnumber" )
codeInfo[SONGDISABLED] =	( SONGDISABLED, BYTE, "daap.songdisabled" )
codeInfo[SONGEQPRESET] =	( SONGEQPRESET, STRING, "daap.songeqpreset" )
codeInfo[SONGFORMAT] =		( SONGFORMAT, STRING, "daap.songformat" )
codeInfo[SONGGENRE] =		( SONGGENRE, STRING, "daap.songgenre" )
codeInfo[SONGDESCRIPTION] =	( SONGDESCRIPTION, STRING, "daap.songdescription" )
codeInfo[SONGRELATIVEVOLUME] =	( SONGRELATIVEVOLUME, BYTE, "daap.songrelativevolume" )
codeInfo[SONGSAMPLERATE] =	( SONGSAMPLERATE, INT, "daap.songsamplerate" )
codeInfo[SONGSIZE] =		( SONGSIZE, INT, "daap.songsize" )
codeInfo[SONGSTARTTIME] =	( SONGSTARTTIME, INT, "daap.songstarttime " )
codeInfo[SONGSTOPTIME] =	( SONGSTOPTIME, INT, "daap.songstoptime " )
codeInfo[SONGTIME] =		( SONGTIME, INT, "daap.songtime" )
codeInfo[SONGTRACKCOUNT] =	( SONGTRACKCOUNT, SHORT, "daap.songtrackcount" )
codeInfo[SONGTRACKNUMBER] =	( SONGTRACKNUMBER, SHORT, "daap.songtracknumber" )
codeInfo[SONGUSERRATING] =	( SONGUSERRATING, BYTE, "daap.songuserrating" )
codeInfo[SONGYEAR] =		( SONGYEAR, SHORT, "daap.songyear" )
codeInfo[SONGDATAKIND] =	( SONGDATAKIND, BYTE, "daap.songdatakind" )
codeInfo[SONGDATAURL] =		( SONGDATAURL, STRING, "daap.songdataurl" )
codeInfo[DATABASEPLAYLISTS] =	( DATABASEPLAYLISTS, LIST, "daap.databaseplaylists" )
codeInfo[BASEPLAYLIST] =	( BASEPLAYLIST, BYTE, "daap.baseplaylist" )
codeInfo[PLAYLISTSONGS] =	( PLAYLISTSONGS, LIST, "daap.playlistsongs" )
codeInfo[RESOLVE] =		( RESOLVE, LIST, "daap.resolve" )
codeInfo[RESOLVEINFO] =		( RESOLVEINFO, LIST, "daap.resolveinfo" )
codeInfo[NORM] =		( NORM, INT, "com.apple.itunes.norm-volume" )
codeInfo[SMART] =		( SMART, BYTE, "com.apple.itunes.smart-playlist" )
